from Bases import setID
from osc_base import OSCBaseObject
from ..Attributes import Attribute
from ..Groups.AttributeGroup import DimmerGroup, ColorGroup, PositionGroup, ParameterGroup

class Device(OSCBaseObject):
    '''Base for all devices.
    :Parameters:
        'name' : default is empty
        'id' : Universal ID.  Default is None (which assigns a new uuid)
        'description' : default is empty
        'manufacturer' : default is empty
        'channel_count' : int, maximum number of channels for the device.
            default is 0
        'class_name' : class specification (in progress)
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the device.  default is None.
    '''
    
    _saved_class_name = 'Device'
    _saved_attributes = ['name', 'base_class', 'id', 'description', 'manufacturer', 'personality_mode']
    _saved_child_objects = ['Attributes']
    def __init__(self, **kwargs):
        self._name = None
        self._manufacturer = None
        self.attr_by_chan = {}
        super(Device, self).__init__(**kwargs)
        self.register_signal('new_attribute', 'del_attribute', 'child_added')
        if 'deserialize' not in kwargs:
            self.Attributes = {}
            self.name           = kwargs.get('name', '')
            self.manufacturer   = kwargs.get('manufacturer', '')
            self.description    = kwargs.get('description', '')
            self.personality_mode = kwargs.get('personality_mode', '')
            self.id = setID(kwargs.get('id'))
            if hasattr(self, 'base_class') is False:
                self.base_class = kwargs.get('base_class')
            categories = kwargs.get('categories',{})
            for category in categories.itervalues():
                self.add_category(category)
        self.register_attr_watch('name')
        self.register_attr_watch('manufacturer')
        self.register_attr_watch('description')
        self.register_attr_watch('personality_mode')
        
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, value):
        self._name = value
        sig = 'attr_watch_name'
        if sig in self._emitters:
            self.emit(sig, object=self, name='name', value=value)
    @property
    def manufacturer(self):
        return self._manufacturer
    @manufacturer.setter
    def manufacturer(self, value):
        self._manufacturer = value
        sig = 'attr_watch_manufacturer'
        if sig in self._emitters:
            self.emit(sig, object=self, name='name', value=value)

class DefinedDevice(Device):
    '''A Device Definition
    
    :Events:
        'new_attribute' (device_id, attribute_id)
    '''
    
    _saved_class_name = 'DefinedDevice'
    _saved_attributes = ['manufacturer', 'channel_count']
    def __init__(self, **kwargs):
        self.GlobalAttributes = kwargs.get('GlobalAttributes')
        self._channel_count = 0
        super(DefinedDevice, self).__init__(**kwargs)
        self.register_signal('chan_update', 'group_added', 'definition_update')
        self.register_attr_watch('manufacturer')
        self.register_attr_watch('channel_count')
        if 'deserialize' not in kwargs:
            self.manufacturer   = kwargs.get('manufacturer', '')
            self.channel_count  = kwargs.get('channel_count', 0)
        if not hasattr(self, 'Attributes'):
            self.Attributes = {}
        self._update_attributes()
        
    def add_attribute(self, **kwargs):
        '''
        :Parameters:
            'attribute' : instance of a global attribute to copy from.
                this will soon be changed
            'chan_index' : int, relative channel index for the new attribute
            'id' : id for the new attribute.  Default is None (which assigns a new uuid)
        '''
        
        kwargs.setdefault('chan_index', self.find_max_channel() + 1)
        attribute   = kwargs.get('attribute')
        kwargs.update({'parent_device_id':self.id, 'osc_address':attribute.name})
        kwargs.update({'GlobalAttribute':attribute})
        self.add_child_object(kwargs)
        kwargs.update({'root_category':self.root_category.parent.subcategories['attribute']})
        new_attribute = Attribute.DefinitionAttribute(**kwargs)
        new_attribute.connect('chan_index_changed', self.on_attribute_chan_index_changed)
        self.Attributes.update({new_attribute.id:new_attribute})
        self._update_attributes()
        self.emit('new_attribute', device_id=self.id, attribute_id=new_attribute.id)
        self.emit('child_added', device_id=self.id, attribute_id=new_attribute.id)
        return new_attribute
    
    def del_attribute(self, **kwargs):
        attrid = kwargs.get('id')
        attribute = self.Attributes.get(attrid)
        if attribute:
            attribute.disconnect(callback=self.on_attribute_chan_index_changed)
            del self.Attributes[attrid]
            self._update_attributes()
            self.emit('del_attribute', device_id=self.id, attribute_id=attrid, device=self)
        
    @property
    def channel_count(self):
        return self._channel_count
    @channel_count.setter
    def channel_count(self, value):
        value = int(value)
        if value >= self.find_max_channel():
            self._channel_count = value
    
    def _update_attributes(self):
        self.attr_by_chan.clear()
        for key, val in self.Attributes.iteritems():
            self.attr_by_chan.update({val.chan_index:val})
        max_chan = self.find_max_channel()
        if max_chan > self.channel_count:
            self.channel_count = max_chan
##        if not hasattr(self, 'Color'):
##            color_attribs = {}
##            for attrib in self.Attributes.itervalues():
##                if attrib.name.lower() in ['red', 'green', 'blue']:
##                    color_attribs.update({attrib.name.lower():attrib})
##            if len(color_attribs) == 3:
##                self.add_color_group(color_attribs)
#            
#    def add_color_group(self, attribs):
#        factory = DeviceSystem.DeviceFactory()
#        kwargs = {'Name':'Color', 'parent_device':self, 'osc_address':'Color'}
#        self.add_child_object(kwargs)
#        group = factory.make(type='ColorGroupRGB', base=self.base_class, kwargs=kwargs)
#        for key, val in attribs.iteritems():
#            group.Attributes.update({val.id:val})
#            group.set_color_map(attribute=val, color=key)
#        self.Color = group
#        self.emit('group_added', device=self, group=group)
    def find_max_channel(self):
        if len(self.attr_by_chan) == 0:
            return 0
        return max(self.attr_by_chan.keys())
        
    def on_attribute_chan_index_changed(self, **kwargs):
        self._update_attributes()
        self.emit('chan_update', device=self, attribute=kwargs.get('attribute'))
        
    def _deserialize_child(self, d):
        if d['saved_class_name'] == 'DefinitionAttribute':
            kwargs = {}
            if self.osc_enabled:
                kwargs.update({'osc_address':d['attrs'].get('osc_address')})
            self.add_child_object(kwargs)
            kwargs.update({'root_category':self.root_category.parent.subcategories['attribute']})
            gl_attrib = self.GlobalAttributes.get(d['attrs']['GlobalAttribute_id'])
            kwargs.update({'GlobalAttribute':gl_attrib, 'deserialize':d})
            attribute = Attribute.DefinitionAttribute(**kwargs)
            #self.emit('new_attribute', device_id=self.id, attribute_id=attribute.id)
            return attribute
        return super(Device, self)._deserialize_child(d)
        

class PatchedDevice(Device):
    '''A Device that has been patched
    
    :Parameters:
        '_DefinedDevice_' : instance of a device definition to use for the new device.
            this will change soon
        'definition_id' : id of the device definition used to make this device. (for later use)
        'device_num' : int, index of the device to be used in the UI (for organization)
        'universe_id' : id of the DMX universe this device belongs to
        'start_chan' : int, starting DMX channel
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the device.  default is None.
    '''
    
    _saved_class_name = 'PatchedDevice'
    _saved_attributes = ['device_num', 'universe_id', 'start_chan', 'definition_id']
    _saved_child_objects = ['Groups']
    signals_to_register = ['device_num_update', 'check_device_num']
    group_classes = [PositionGroup, ColorGroup, DimmerGroup]
    def __init__(self, **kwargs):
        self.definition = kwargs.get('definition')
        self.ungrouped_attributes = {}
        #self.saved_child_objects = set(['Groups'])
        self._device_num = None
        super(PatchedDevice, self).__init__(**kwargs)
        self.add_osc_child_nodes()
        if 'deserialize' not in kwargs:
            self.definition_id = getattr(self.definition, 'id', None)
            self._device_num = kwargs.get('device_num')
            self.start_chan = kwargs.get('start_chan')
            self.universe_id = kwargs.get('universe_id')
            self.Groups = {}
            for grpcls in self.group_classes:
                key = grpcls._saved_class_name
                group = grpcls(osc_parent_node=self.group_osc_node)
                self.Groups.update({key:group})
        else:
            self._device_num = kwargs.get('deserialize')['attrs']['device_num']
        if self.definition:
            self._init_attributes(self.definition.Attributes.copy())
            for key in ['name', 'description']:
                if getattr(self, key) == '':
                    setattr(self, key, getattr(self.definition, key))
            self.definition.connect('definition_update', self.on_definition_update)
        
    @property
    def device_num(self):
        return self._device_num
    @device_num.setter
    def device_num(self, value):
        value = int(value)
        old = self._device_num
        self.emit('check_device_num', device=self, value=value, old=old)
    def _set_device_num(self, value):
        old = self._device_num
        self._device_num = value
        self.emit('device_num_update', device=self, value=value, old=old)
            
    @property
    def end_chan(self):
        start = self.start_chan
        if start is not None:
            return start + self.channel_count - 1
        return None
            
    def repatch(self, **kwargs):
        univ_id = kwargs.get('universe_id')
        start = kwargs.get('start_chan')
        self.attr_by_chan.clear()
        for attrib in self.Attributes.itervalues():
            attrib.repatch(**kwargs)
            self.attr_by_chan.update({attrib.chan:attrib})            
        
    def add_osc_child_nodes(self):
        if not hasattr(self, 'attribute_osc_node'):
            self.attribute_osc_node = self.osc_node.add_new_node(name='Attributes')
        if not hasattr(self, 'group_osc_node'):
            self.group_osc_node = self.osc_node.add_new_node(name='Groups')
            
    def _deserialize_child(self, d):
        if d['attrs']['base_class'] == 'Group':
            self.add_osc_child_nodes()
            cls_d = dict(zip([cls._saved_class_name for cls in self.group_classes], self.group_classes))
            obj = cls_d[d['saved_class_name']](deserialize=d, osc_parent_node=self.group_osc_node)
            return obj
        #return super(PatchedDevice, self)._deserialize_child(d)
    
    @property
    def channel_count(self):
        return self.definition.channel_count

    def _init_attributes(self, attributes):
        new_kwargs = {'universe_id':self.universe_id, 'parent_device_id':self.id}
        self.Attributes.clear()
        self.attr_by_chan.clear()
        for key, val in attributes.iteritems():
            chan = self.start_chan + val.chan_index -1
            new_kwargs.update({'chan':chan, 'osc_address':val.name, 'osc_parent_node':self.attribute_osc_node})
            new_kwargs.update({'root_category':self.root_category.parent.subcategories['attribute']})
            new_kwargs.update({'DefinitionAttribute':val})
            new_attribute = Attribute.PatchedAttribute(**new_kwargs)
            
            self.Attributes.update({new_attribute.id:new_attribute})
            self.attr_by_chan.update({new_attribute.chan:new_attribute})
            
            self.emit('new_attribute', device_id=self.id, attribute_id=new_attribute.id)
            self.emit('child_added', device_id=self.id, attribute_id=new_attribute.id)
            
        for g in self.Groups.itervalues():
            g.device = self
            g.search_for_members()
        if not hasattr(self, 'ParameterGroups'):
            self.ParameterGroups = {}
        for key, val in self.Attributes.iteritems():
            if len(val.users) == 0:
                self.ungrouped_attributes.update({key:val})
            group = ParameterGroup(device=self, attribute_name=val.name, osc_parent_node=self.group_osc_node)
            if group.valid:
                self.ParameterGroups.update({val.name:group})
                
    def on_definition_update(self, **kwargs):
        pass
